package main

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/sirupsen/logrus"
	"github.com/ingopansa/userinfoservice/storage"
	"github.com/ingopansa/userinfoservice/storage/memory"
	"github.com/ingopansa/userinfoservice/storage/gorm"

	"github.com/ingopansa/userinfoservice/tokenprovider"
	"github.com/ingopansa/userinfoservice/tokenprovider/local"
	"github.com/ingopansa/userinfoservice/tokenprovider/remote"
)

// Config is the config format for the main application.
type Config struct {
	Issuer    		string    		`json:"issuer"`
	Storage   		Storage   		`json:"storage"`

	Telemetry 		Telemetry 		`json:"telemetry"`
	Expiry    		Expiry    		`json:"expiry"`
	Logger    		Logger    		`json:"logger"`
	HTTP	  		HTTP			`json:"http"`

	TokenProviders	[]TokenProvider `json:"tokenproviders"`
	Endpoints		[]Endpoint		`json:"endpoints"`
}


// HTTP is the config format for the HTTP server.
type HTTP struct {
	HTTP           string   `json:"http"`
	HTTPS          string   `json:"https"`
	TLSCert        string   `json:"tlsCert"`
	TLSKey         string   `json:"tlsKey"`
	AllowedOrigins []string `json:"allowedOrigins"`
}

// Telemetry is the config format for telemetry including the HTTP server config.
type Telemetry struct {
	HTTP string `json:"http"`
}

type Endpoint struct {
	Id		string			`json:"id"`
	Config	EndpointConfig	`json:"config"`
}

type EndpointConfig struct {
	Name			string		`json:"string"`
	BasePath		string		`json:"basepath"`
	TokenProviderID	string		`json:"tokenproviderid"`
}


/*
 * Storage, StorageConfig and storages define the data structure and interface for integrating different storage backends
 */

// Storage holds app's storage configuration.
type Storage struct {
	Type   string        `json:"type"`
	Config StorageConfig `json:"config"`
}

// StorageConfig is a configuration that can create a storage.
type StorageConfig interface {
	Open(logrus.FieldLogger) (storage.Storage, error)
}

var storages = map[string]func() StorageConfig{
	"memory":     	func() StorageConfig { return new(memory.Config) },
	"gorm":			func() StorageConfig { return new(gorm.GormConfig)},
}

// UnmarshalJSON allows Storage to implement the unmarshaler interface to
// dynamically determine the type of the storage config.
func (s *Storage) UnmarshalJSON(b []byte) error {
	var store struct {
		Type   	string          `json:"type"`
		Config 	json.RawMessage `json:"config"`
	}
	if err := json.Unmarshal(b, &store); err != nil {
		return fmt.Errorf("parse storage: %v", err)
	}
	f, ok := storages[store.Type]
	if !ok {
		return fmt.Errorf("unknown storage type %q", store.Type)
	}

	storageConfig := f()
	if len(store.Config) != 0 {
		data := []byte(os.ExpandEnv(string(store.Config)))
		if err := json.Unmarshal(data, storageConfig); err != nil {
			return fmt.Errorf("parse storage config: %v", err)
		}
	}
	*s = Storage{
		Type:   store.Type,
		Config: storageConfig,
	}
	return nil
}

/*
 * TokenProvider, TokenProviderConfig and tokenproviders define the data structure and interface for integrating different token providers
 */
type TokenProvider struct {
	Id		string				`json:"id"`
	Type 	string 				`json:"type"`
	Config 	TokenProviderConfig `json:"config"`
}

type TokenProviderConfig interface{
	Open(logrus.FieldLogger)(tokenprovider.TokenProvider, error)
}

var tokenProviders = map[string]func() TokenProviderConfig{
	"local": func() TokenProviderConfig{ return new(local.Config)},
	"remote": func() TokenProviderConfig{ return new(remote.Config)},
}

func (t *TokenProvider) UnmarshalJSON(b []byte) error{
	var tokenProvider struct {
		Id		string			`json:"id"`
		Type   	string          `json:"type"`
		Config 	json.RawMessage `json:"config"`
	}

	if err := json.Unmarshal(b, &tokenProvider); err != nil {
		return fmt.Errorf("parse tokenprovider: %v", err)
	}

	f, ok := tokenProviders[tokenProvider.Type]
	if !ok {
		return fmt.Errorf("unknown tokenprovider type %q", tokenProvider.Type)
	}

	tokenProviderConfig := f()
	if len(tokenProvider.Config) != 0 {
		data := []byte(os.ExpandEnv(string(tokenProvider.Config)))
		if err := json.Unmarshal(data, tokenProviderConfig); err != nil {
			return fmt.Errorf("parse tokenprovider config: %v", err)
		}
	}
	*t = TokenProvider{
		Id:		tokenProvider.Id,
		Type:   tokenProvider.Type,
		Config: tokenProviderConfig,
	}
	return nil
}


// Expiry holds configuration for the validity period of components.
type Expiry struct {
	// SigningKeys defines the duration of time after which the SigningKeys will be rotated.
	SigningKeys string `json:"signingKeys"`

	// IdTokens defines the duration of time for which the IdTokens will be valid.
	IDTokens string `json:"idTokens"`
}

// Logger holds configuration required to customize logging for dex.
type Logger struct {
	// Level sets logging level severity.
	Level string `json:"level"`

	// Format specifies the format to be used for logging.
	Format string `json:"format"`
}
